/*!
 * Copyright (c) 2010 Jacob Essex
 *
 * Dual licenced under MIT or GPL 3 licneces
 * 
 * See the GPL3 or MIT files in the main directory for details
 */

/**
 * This is a wrapper arround Soapi that simplifies everything into an acceptable interface
 *
 * The main change is making the interface more "JS" ish by removing everything to do with pages and
 * just having each object implement an each method which calls a function on every object within
 * a range. This allows a simpler interface and more efficiant use of the API (and peoples bandwidth)
 */
require.def('se/api', ['config', 'log/api', 'lib/soapi'],
function(Config, Log){
    //todo fix, don't like this. No elegance
    //the logical side of my brain points out that it works :P
    var arraySplit = function(arr, size){
        var result = [];
        var current = [];
        for ( var i = 0; i < arr.length; i++ ){
            current.push(arr[i]);
            if ( current.length >= size ){
                result.push(current);
                current = [];
            }
        }
        result.push(current);
        return result;
    };

    var RangeObject = function(){
        var that = this;

        this.getStart = function(){
            return 0;
        };
        this.shouldHalt = function(){
            return false;
        };
        this.getPageSize = function(){
            return Config.MaxPageSize;
        };

        this.from = function(start){
            that.getStart = function(){ return start; };
            return that;
        }
        this.to = function(end){
            that.shouldHalt = function(o, i){ return i >= end; };
            return that;
        }
        this.range = function(start, length){ 
            that.from(start)
                .to(start + length);
            return that;
        }
        this.until = function(f){
            that.shouldHalt = f;
            return that;
        }

    }
    return {

        /**
         * Get items in a set range
         *
         * @param start the starting index
         * @param length the length of the data to get
         */
        range: function(start, length){
            return (new RangeObject()).range(start, length);
        },
        /**
         * get data from a specifics index
         */
        from: function(start){
            return (new RangeObject()).from(start);
        },
        /**
         * Get data until the function returns true. If the function returns true, it stops
         * after the current item
         *
         * @param f the function. It should take two parameters, the first 
         *              being the current data and the second being the current
         *              index
         */
        until: function(f){
            return (new RangeObject()).until(start);
        },

        Api: function(url){
            var that = this;
            var factory = new Soapi.RouteFactory(url, Config.ApiKey);

            var doEach = function(reqObj, range, callbacks){
                reqObj.pagesize = range.getPageSize();
                reqObj.page = Math.floor(range.getStart() / reqObj.pagesize) + 1; 
                var offset = range.getStart() % reqObj.pagesize 
                Log.info('Page: ' + reqObj.page + ' PageSize:' + reqObj.pagesize);
                doEachImpl(reqObj, offset, range, callbacks);
            }

            /**
             * This function abstracts away the fact that there are pages, allowing a simpler interface as well
             * as more optimal use of bandwidth. 
             */
            var doEachImpl = function(reqObj, offset, range, callbacks){

                reqObj.getResponse(function(data){
                    var dataArray = data.items;
                    
                    Log.info('Got ' + dataArray.length + ' items of data');

                    for ( var i = offset; i < dataArray.length; i++ ){
                        //this is now first as a fix so we don't request another page only to halt. It just
                        //requests a modification to the halt functions
                        callbacks && callbacks.step && callbacks.step(dataArray[i]);

                        if ( range.shouldHalt(dataArray[i], (reqObj.page-1)*reqObj.pagesize + i) ){
                            Log.info('Haled on index ' + i + ' of page ' + reqObj.page);
                            callbacks && callbacks.done && callbacks.done();
                            return;
                        }
                    }

                    //assuming we have reached the end...
                    if ( reqObj.pagesize != dataArray.length || dataArray.length == 0 ){
                        Log.info('End reached');
                        callbacks && callbacks.done && callbacks.done();
                        return;
                    }

                    reqObj.page++;

                    doEachImpl(reqObj, 0, range, callbacks);
                },
                function(data){
                    callbacks && callbacks.error && callbacks.error(data.message);
                },
                Config.SeApiTimeout);
            }

            this.UnansweredQuestions = function(options, range){
                var qs = factory.QuestionsUnanswered().applyParameters(options);
                return {
                    each : function(callbacks){
                        doEach(qs, range, callbacks);
                    }
                }
            }

            this.Questions = function(options, range){
                var qs = factory.Questions().applyParameters(options);
                return {
                    each : function(callbacks){
                        doEach(qs, range, callbacks);
                    }
                }
            }


            var callSplit = function(func, options, ids, range, callbacks){
                if ( !ids.length ){
                    callbacks.done && callbacks.done();
                    return; 
                }
                var idarray = arraySplit(ids, Config.MaxVectorSize);

                var process = function(){
                    var id = idarray.pop();
                    var qs = func.apply(factory, id).applyParameters(options);
                    doEach(qs, range, callbacks);
                }

                //we need to call the done callback when everything is done, if we don't do it this
                //way it is called every time a section is done
                //the Chain method doesn't work here due to the callbacks, which is a pita
               var df = callbacks.done;
               callbacks.done = function(){
                   if ( idarray.length == 0 ){
                       df && df();
                       return;
                   }
                   process();
               }
               process();
            }

            this.Users = function(options, ids, range){
                return{
                    each : function(callbacks){
                        callSplit(factory.UsersById, options, ids, range, callbacks);
                    }
                }
            }

            /**
             * @param ids an array of ids to get questions for. This will batch up the questions to
             *            try and reduce the number of requests
             */
            this.QuestionsById = function(options, ids, range){
                return {
                    each : function(callbacks){
                        callSplit(factory.QuestionsById, options, ids, range, callbacks);
                    }
                };
            }

            this.Sites = function(options, range){
                var f = factory.Sites().applyParameters(options);
                return {
                    each : function(callbacks){
                        doEach(f, range, callbacks);
                    }
                }
            }
            this.Tags = function(options, range){
                var f = factory.Tags().applyParameters(options);
                return {
                    each : function(callbacks){
                        doEach(f, range, callbacks);
                    }
                }
            }

            this.AnswersByUser = function(options, uid, range){
                var ans = factory.UsersByIdAnswers(uid);

                return {
                    each : function(callback, error){
                        doEach(ans, range, callback, error);
                    }
                }
            }
            
        }
    }
});      
